import time
from decimal import Decimal

import requests
from django.conf import settings

from libs.utils import AttrDict
from libs.wx.utils import PayUtil

pay_info = AttrDict(settings.SERVICES['WX_PAY'])


class WxPay(object):
    @classmethod
    def js_api_params(cls, prepay_id: str) -> dict:
        params = {
            'appId': pay_info.APP_ID,
            'timeStamp': str(int(time.time())),
            'nonceStr': PayUtil.nonce_str(),
            'package': 'prepay_id=' + prepay_id,
            'signType': 'MD5',
        }
        # 添加日志记录
        print('微信支付参数(签名前):', params)
        PayUtil.add_signature(pay_info.MCH_KEY, 'paySign', params)
        print('微信支付参数(签名后):', params)
        return params

    @classmethod
    def h5_unified_order(cls, order_sn: str, goods_name: str, total_fee: int):
        api = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
        params = {
            'appid': pay_info.APP_ID,
            'mch_id': pay_info.MCH_ID,
            'nonce_str': PayUtil.nonce_str(),
            'body': goods_name,
            'out_trade_no': order_sn,
            'total_fee': int(Decimal(str(total_fee)) * 100),
            'spbill_create_ip': '127.0.0.1',
            'trade_type': 'MWEB',
            'notify_url': pay_info.NOTIFY_ENDPOINT.rstrip('/') + '/wechat/pay/notify/',
        }
        PayUtil.add_signature(pay_info.MCH_KEY, 'sign', params)
        response = requests.post(api, PayUtil.dict_to_xml(params).encode('utf-8'), timeout=15)
        result, _, _ = PayUtil.parse_pay_response(response.content, pay_info.MCH_KEY)
        return result.mweb_url

    @classmethod
    def unified_order(cls, open_id, order_sn, goods_name, total_fee, attach='') -> str:
        """ 调用微信支付统一下单接口，生成用于微信浏览器内调起支付用的js api参数
        :param open_id: 当前用户的open_id
        :param order_sn: 订单编号，要求32个字符内，只能是数字、大小写字母_-|* 且唯一
        :param goods_name: 商品简单描述，规则 商家名称-销售商品类目， 例如：腾讯充值中心-QQ会员充值
        :param total_fee: 订单总金额，单位元
        :param attach: 额外参数
        :rtype: dict
        """

        api = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
        params = {
            'appid': pay_info.APP_ID,
            'mch_id': pay_info.MCH_ID,
            'nonce_str': PayUtil.nonce_str(),
            'openid': open_id,
            'body': goods_name,
            'out_trade_no': order_sn,
            'total_fee': int(Decimal(str(total_fee)) * 100),
            'spbill_create_ip': '127.0.0.1',
            'trade_type': 'JSAPI',
            'attach': attach,
            'notify_url': pay_info.NOTIFY_ENDPOINT.rstrip('/') + '/wechat/pay/notify/',
        }
        PayUtil.add_signature(pay_info.MCH_KEY, 'sign', params)
        response = requests.post(api, PayUtil.dict_to_xml(params).encode('utf-8'), timeout=15)
        result, _, _ = PayUtil.parse_pay_response(response.content, pay_info.MCH_KEY)
        return result.prepay_id

    @classmethod
    def refund(cls, order_sn: str, refund_sn: str, total_fee: float, refund_fee: float,
               use_balance: bool = False) -> bool:
        """ 申请退款，默认先尝试通过未结算资金退款，如果失败则尝试用余额退款

        :param order_sn: 下单时提供的订单号
        :param refund_sn: 退款单号，要求32个字符内，只能是数字、大小写字母_-|* 且唯一
        :param total_fee: 订单金额，单位元
        :param refund_fee: 退款金额，单位元
        :param use_balance: 资金来源，是否使用余额退款，默认使用未结算资金退款，
        :return: None
        """

        api = 'https://api.mch.weixin.qq.com/secapi/pay/refund'
        params = {
            'appid': pay_info.APP_ID,
            'mch_id': pay_info.MCH_ID,
            'nonce_str': PayUtil.nonce_str(),
            'sign_type': 'MD5',
            'out_trade_no': order_sn,
            'out_refund_no': refund_sn,
            'total_fee': int(total_fee * 100),
            'refund_fee': int(refund_fee * 100),
            'refund_account': 'REFUND_SOURCE_RECHARGE_FUNDS' if use_balance else 'REFUND_SOURCE_UNSETTLED_FUNDS',
        }
        PayUtil.add_signature(pay_info.MCH_KEY, 'sign', params)
        response = requests.post(api, PayUtil.dict_to_xml(params), cert=(pay_info.MCH_CERT_PATH, pay_info.MCH_KEY_PATH))
        result, is_success, exception_msg = PayUtil.parse_pay_response(response.content, pay_info.MCH_KEY, False)
        # 如果返回错误代码为：xxx，则未结算资金不足以支付退款，尝试通过余额退款
        if not is_success:
            if (not use_balance) and result.get('err_code') == 'xxx':
                return WxPay.refund(order_sn, refund_sn, total_fee, refund_fee, True)
            else:
                raise Exception(exception_msg)

    @staticmethod
    def order_query(order_sn: str, abort: bool = True):
        """ 订单状态查询

        :param order_sn: 下单时提供的订单号
        :param abort: 是否出错时抛异常
        :return: trade_state: SUCCESS 支付成功/REFUND 已退款/NOTPAY 未支付/CLOSED 已关闭/PAYERROR 支付失败
        """

        api = 'https://api.mch.weixin.qq.com/pay/orderquery'
        params = {
            'appid': pay_info.APP_ID,
            'mch_id': pay_info.MCH_ID,
            'nonce_str': PayUtil.nonce_str(),
            'sign_type': 'MD5',
            'out_trade_no': order_sn,
        }
        PayUtil.add_signature(pay_info.MCH_KEY, 'sign', params)
        response = requests.post(api, PayUtil.dict_to_xml(params))
        result, is_success, _ = PayUtil.parse_pay_response(response.content, pay_info.MCH_KEY, abort)
        return result if is_success else None

    @staticmethod
    def close_order(order_sn: str, abort: bool = True):
        """ 关闭订单

        :param order_sn: 下单时提供的订单号
        :param abort: 是否出错时抛异常
        :return: None
        """

        api = 'https://api.mch.weixin.qq.com/pay/closeorder'
        params = {
            'appid': pay_info.APP_ID,
            'mch_id': pay_info.MCH_ID,
            'nonce_str': PayUtil.nonce_str(),
            'sign_type': 'MD5',
            'out_trade_no': order_sn,
        }
        PayUtil.add_signature(pay_info.MCH_KEY, 'sign', params)
        response = requests.post(api, PayUtil.dict_to_xml(params))
        PayUtil.parse_pay_response(response.content, pay_info.MCH_KEY, abort)
